﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ZackFlame.SimpleBrainFuck.TextResources;

namespace ZackFlame.SimpleBrainFuck.Core
{
    internal static class SystemFunctions
    {
        /// <summary>
        /// Вызывает ошибку трансляции с текстом text.
        /// </summary>
        public static string Error(string text)
        {
            throw new CodeErrorException(0,
                string.Format(CodeErrors.Call_UserDefinedError, text));
        }

        /// <summary>
        /// Сдвиг на k ячеек вправо или на -k ячеек влево.
        /// </summary>
        public static string Shift(int k)
        {
            char direction = k > 0 ? '>' : '<';
            return new string(direction, Math.Abs(k));
        }

        /// <summary>
        /// Обнуление текущей ячейки.
        /// </summary>
        public static string Zero()
        {
            return "[-]";
        }

        /// <summary>
        /// Прибавление значения ячейки n (текущей) к значению ячейки n+k.
        /// </summary>
        public static string Add(int k)
        {
            return "[-" + Shift(k) + "+" + Shift(-k) + "]";
        }

        /// <summary>
        /// Вычитает значение ячейки n (текущей) из значения ячейки n+k.
        /// </summary>
        public static string Sub(int k)
        {
            return "[-" + Shift(k) + "-" + Shift(-k) + "]";
        }

        /// <summary>
        /// Копирование значения ячейки n (текущей) в ячейку n+k с потерей
        /// (обнулением) значения ячейки n.
        /// </summary>
        public static string Move(int k)
        {
            return Shift(k) + Zero() + Shift(-k) + Add(k);
        }

        /// <summary>
        /// Копирование значения ячейки n (текущей) в ячейку n+k c использованием 
        /// промежуточной ячейки n+k+t, благодаря чему значение ячейки n
        /// не теряется (сохраняется).
        /// </summary>
        public static string Copy(int k, int t)
        {
            return Shift(k) + Zero() + Shift(t) + Zero() + Shift(-k - t) + "[-" + Shift(k) + "+" +
                Shift(t) + "+" + Shift(-k - t) + "]" + Shift(k + t) + Move(-k - t) + Shift(-k - t);
        }

        /// <summary>
        /// Задаёт условие для выполнения блока кода с использованием
        /// промежуточной ячейки n+t: если текущая ячейка n не равна нулю, то
        /// выполняется условие; используется только вместе с Else и EndIf.
        /// </summary>
        public static string If(int t)
        {
            return Shift(t) + Zero() + "+" + Shift(-t) + "[";
        }

        /// <summary>
        /// См. описание If.
        /// </summary>
        public static string Else(int t)
        {
            return Shift(t) + Zero() + Shift(-t) + Zero() + "]" + Shift(t) + "[" + Shift(-t);
        }

        /// <summary>
        /// См. описание If.
        /// </summary>
        public static string EndIf(int t)
        {
            return Shift(t) + Zero() + "]" + Shift(-t);
        }

        /// <summary>
        /// Повторяет заданную строку text k раз.
        /// </summary>
        public static string Repeat(string text, int k)
        {
            StringBuilder result = new StringBuilder();
            
            for (int i = 0; i < k; i++)
            {
                result.Append(text);
            }

            return result.ToString();
        }

        /// <summary>
        /// Цикл, выполняющийся пока n+k не равно нулю, где n - текущая 
        /// ячейка; используется только вместе с EndWhile.
        /// </summary>
        public static string While(int k)
        {
            return Shift(k) + "[" + Shift(-k);
        }

        /// <summary>
        /// См. описание While.
        /// </summary>
        public static string EndWhile(int k)
        {
            return Shift(k) + "]" + Shift(-k);
        }

        /// <summary>
        /// Сокращённое введение числовой константы k в текущую ячейку n с
        /// использованием промежуточной ячейки n+t.
        /// </summary>
        public static string Value(int k, int t)
        {
            StringBuilder result = new StringBuilder(Zero());
            
            if (k <= 10)
            {
                result.Append(new string('+', k));
            }
            else
            {
                if (k <= 256)
                {
                    int[] factors = MathHelper.Factorization(k);
                    bool addOne = false;
                    if (factors.Length == 1)
                    {
                        factors = MathHelper.Factorization(k - 1);
                        addOne = true;
                    }
                    if (factors.Length > 2)
                    {
                        for (int i = 1; i < factors.Length - 1; i++)
                        {
                            factors[0] *= factors[i];
                        }
                    }

                    result
                        .Append(new string('+', factors[0]))
                        .Append(Shift(t))
                        .Append(Zero())
                        .Append(Shift(-t))
                        .Append("[-")
                        .Append(Shift(t))

                        .Append(new string('+', factors[factors.Length - 1]))
                        .Append(Shift(-t))
                        .Append("]")
                        .Append(Shift(t))
                        .Append(Move(-t))
                        .Append(Shift(-t))
                        .Append(addOne ? "+" : string.Empty);
                }
                else
                {
                    throw new ArgumentOutOfRangeException("k", k,
                        "Число для ввода должно быть меньше или равно 256.");
                }
            }

            return result.ToString();
        }

        /// <summary>
        /// Ввод строки символов text длиной len, начиная с текущей ячейки n, с 
        /// использованием промежуточной ячейки n+len.
        /// </summary>
        public static string Text(string text)
        {
            StringBuilder result = new StringBuilder();
            byte[] chars = Encoding.Default.GetBytes(text);

            try
            {
                if (chars.Length > 0)
                {
                    for (int i = 0; i < chars.Length - 1; i++)
                    {
                        result
                            .Append(Value(chars[i], 1))
                            .Append(Shift(+1));
                    }

                    result
                        .Append(Value(chars[chars.Length - 1], 1))
                        .Append(Shift(-chars.Length + 1));
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                throw new ArgumentException("Строка должна включать только символы с кодом меньше 256.");
            }

            return result.ToString();
        }

        /// <summary>
        /// Печать строки символов text с использованием промежуточной ячейки n+t,
        /// где n - текущая ячейка.
        /// </summary>
        public static string Type(string text, int t)
        {
            StringBuilder result = new StringBuilder();
            byte[] chars = Encoding.Default.GetBytes(text);

            try
            {
                if (chars.Length > 0)
                {
                    int current = chars[0];
                    result
                        .Append(Value(current, t))
                        .Append('.');

                    for (int i = 1; i < chars.Length; i++)
                    {
                        int diff = (int)chars[i] - current;
                        current = chars[i];
                        char ch = diff > 0 ? '+' : '-';
                        result
                            .Append(new string(ch, Math.Abs(diff)))
                            .Append('.');
                    }
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                throw new ArgumentException("Строка должна включать только символы с кодом меньше 256.");
            }

            return result.ToString();
        }

        /// <summary>
        /// Печать строки символов длиной k (при k &lt; 0 - печать в обратном порядке).
        /// </summary>
        public static string Print(int k)
        {
            StringBuilder result = new StringBuilder();

            if (k != 0)
            {
                string action = "." + Shift(Math.Sign(k));
                for (int i = 0; i < Math.Abs(k) - 1; i++)
                {
                    result.Append(action);
                }

                result
                    .Append('.')
                    .Append(Shift(-k + Math.Sign(k)));
            }

            return result.ToString();
        }
    }
}
